/** 《俄罗斯方块类》
 * 1 属性:
 *   # data
 *   # type
 *   # present
 *   # predict
 *   # base
 * 2 方法：
 *   # next(behavior, direction, step = 1) => true|false
 *   ## behavior: toMove|toFlip|toRotate
 *   # move(direction='down')
 *   ## direction: "up"|"down"|"left"|"right"
 *   # flip(direction="Y")
 *   ## direction: "X"|"Y"
 *   # rotate(direction="clockwise")
 *   ## direction: "clockwise"|"anticlockwise"
 *   # update()
 * @format */
class Tetris {
    constructor(data) {
        // 只与数据交互，不考虑呈现，不引入 drive 减少依赖
        this.data = data
        // 初始类型：共 7 种
        this.types = "ILlOTZz"
        // 当前指针
        this.present = {
            // 当前像素坐标集：记录了方块在当前状态下每个像素的坐标信息
            // new Array(4).fill('pixel').map(pixel=>new Object({x:0,y:0}))
            pixels: [],
            // 当前矩阵：用于旋转、翻转操作
            matrix: []
        }
        // 预测指针
        this.predict = {
            // 预测像素坐标集：记录操作后方块每个像素的坐标信息
            pixels: [],
            // 预测矩阵
            matrix: []
        }
        // 基础类型：共 7 种
        this.base = {
            I: {
                // 矩阵边长
                length: 4,
                // 不同状态下与屏幕下边界的距离
                moveDown: [0, 2, 0, 1],
                // 初始像素指针
                pixels: [
                    { x: 1, y: 0 },
                    { x: 1, y: 1 },
                    { x: 1, y: 2 },
                    { x: 1, y: 3 }
                ]
            },
            L: {
                length: 3,
                moveDown: [1, 1, 1, 2],
                pixels: [
                    { x: 1, y: 0 },
                    { x: 1, y: 1 },
                    { x: 1, y: 2 },
                    { x: 2, y: 2 }
                ]
            },
            l: {
                length: 3,
                moveDown: [1, 2, 1, 1],
                pixels: [
                    { x: 1, y: 0 },
                    { x: 1, y: 1 },
                    { x: 1, y: 2 },
                    { x: 0, y: 2 }
                ]
            },
            O: {
                length: 2,
                moveDown: [2, 2, 2, 2],
                pixels: [
                    { x: 0, y: 0 },
                    { x: 1, y: 0 },
                    { x: 0, y: 1 },
                    { x: 1, y: 1 }
                ]
            },
            T: {
                length: 3,
                moveDown: [1, 1, 2, 1],
                pixels: [
                    { x: 0, y: 1 },
                    { x: 1, y: 1 },
                    { x: 2, y: 1 },
                    { x: 1, y: 2 }
                ]
            },
            Z: {
                length: 3,
                moveDown: [2, 1, 1, 1],
                pixels: [
                    { x: 0, y: 0 },
                    { x: 1, y: 0 },
                    { x: 1, y: 1 },
                    { x: 2, y: 1 }
                ]
            },
            z: {
                length: 3,
                moveDown: [2, 1, 1, 1],
                pixels: [
                    { x: 1, y: 0 },
                    { x: 2, y: 0 },
                    { x: 0, y: 1 },
                    { x: 1, y: 1 }
                ]
            }
        }
    }

    /* 1 基础函数 */
    // 1.1 随机索引：随机返回 0 ～ length-1 的数组索引
    _randomIndex(length) {
        return Math.floor(Math.random() * length)
    }
    // 1.2 遍历矩阵
    _forMatrix(matrix, callback) {
        for (let i = 0; i < matrix.length; i++) {
            for (let j = 0; j < matrix[0].length; j++) {
                callback(matrix[i][j], this.present.matrix[i][j])
            }
        }
    }
    // 1.3 移动矩阵
    _toMove(matrix, direction, step = 1) {
        switch (direction) {
            case "up":
                this._forMatrix(matrix, (p) => {
                    p.y -= step
                })
                break
            case "down":
                this._forMatrix(matrix, (p) => {
                    p.y += step
                })
                break
            case "left":
                this._forMatrix(matrix, (p) => {
                    p.x -= step
                })
                break
            case "right":
                this._forMatrix(matrix, (p) => {
                    p.x += step
                })
                break
            // default:
        }
    }
    // 1.4 翻转矩阵：Y => 水平（左右）翻转，X => 垂直（上下）翻转，主对角线，副对角线
    _toFlip(matrix, direction) {
        let index = 0
        switch (direction) {
            case "Y":
                index = parseInt(matrix[0].length / 2)
                if (index % 2 === 0) {
                    for (let i = 0; i < matrix.length; i++) {
                        for (let j = 0; index + j < matrix[0].length; j++) {
                            ;[
                                matrix[i][index + j].x,
                                matrix[i][index + j].y,
                                matrix[i][index - 1 - j].x,
                                matrix[i][index - 1 - j].y
                            ] = [
                                matrix[i][index - 1 - j].x,
                                matrix[i][index - 1 - j].y,
                                matrix[i][index + j].x,
                                matrix[i][index + j].y
                            ]
                        }
                    }
                } else {
                    for (let i = 0; i < matrix.length; i++) {
                        for (let j = 1; index + j < matrix[0].length; j++) {
                            ;[
                                matrix[i][index - j].x,
                                matrix[i][index - j].y,
                                matrix[i][index + j].x,
                                matrix[i][index + j].y
                            ] = [
                                matrix[i][index + j].x,
                                matrix[i][index + j].y,
                                matrix[i][index - j].x,
                                matrix[i][index - j].y
                            ]
                        }
                    }
                }
                break
            case "X":
                index = parseInt(matrix.length / 2)
                if (index % 2 === 0) {
                    for (let i = 0; index + i < matrix.length; i++) {
                        for (let j = 0; j < matrix[0].length; j++) {
                            ;[
                                matrix[index - 1 - i][j].x,
                                matrix[index - 1 - i][j].y,
                                matrix[index + i][j].x,
                                matrix[index + i][j].y
                            ] = [
                                matrix[index + i][j].x,
                                matrix[index + i][j].y,
                                matrix[index - 1 - i][j].x,
                                matrix[index - 1 - i][j].y
                            ]
                        }
                    }
                } else {
                    for (let i = 1; index + i < matrix.length; i++) {
                        for (let j = 0; j < matrix[0].length; j++) {
                            ;[
                                matrix[index - i][j].x,
                                matrix[index - i][j].y,
                                matrix[index + i][j].x,
                                matrix[index + i][j].y
                            ] = [
                                matrix[index + i][j].x,
                                matrix[index + i][j].y,
                                matrix[index - i][j].x,
                                matrix[index - i][j].y
                            ]
                        }
                    }
                }
                break
            case "diagonal":
                for (let i = 0; i < matrix.length; i++) {
                    for (let j = 0; j <= i; j++) {
                        i !== j
                            ? ([
                                  matrix[i][j].x,
                                  matrix[i][j].y,
                                  matrix[j][i].x,
                                  matrix[j][i].y
                              ] = [
                                  matrix[j][i].x,
                                  matrix[j][i].y,
                                  matrix[i][j].x,
                                  matrix[i][j].y
                              ])
                            : null
                    }
                }
                break
        }
    }
    // 1.5 旋转矩阵
    _toRotate(matrix, direction, step = 1) {
        for (let i = 0; i < step % 4; i++) {
            switch (direction) {
                // 顺时针旋转
                case "clockwise":
                    this._toFlip(matrix, "diagonal")
                    // 对角线翻转后原来的 Y 会变成 X 轴
                    this._toFlip(matrix, "X")
                    break
                case "anticlockwise":
                    this._toFlip(matrix, "diagonal")
                    this._toFlip(matrix, "Y")
                    break
            }
        }
    }
    // 1.6 更新数据
    _update(power = 1) {
        for (let i = 0; i < 4; i++) {
            this.data[this.present.pixels[i].y][this.present.pixels[i].x] =
                power
        }
    }
    // 1.7 使预测矩阵归位
    _resetPredict() {
        this._forMatrix(this.predict.matrix, (p0, p1) => {
            p0.x = p1.x
            p0.y = p1.y
        })
    }

    /* 2 方块行为函数 */
    // 2.1 预判方块行为合法性：检查预测指针指向的 data 元素是否为 0
    next(behavior, direction, step = 1) {
        this._resetPredict()
        this[behavior](this.predict.matrix, direction, step)
        this._update(0)
        for (let i = 0; i < 4; i++) {
            if (
                !this.data[this.predict.pixels[i].y] ||
                this.data[this.predict.pixels[i].y][
                    this.predict.pixels[i].x
                ] !== 0
            ) {
                this._update(1)
                return false
            }
        }
        this._update(1)
        return true
    }
    // 2.2 移动方块（默认单步）
    move(direction, step = 1) {
        if (this.next("_toMove", direction, step)) {
            this._update(0)
            this._toMove(this.present.matrix, direction, step)
            this._update(1)
        }
    }
    // 2.3 翻转方块
    flip(direction) {
        if (this.next("_toFlip", direction, step)) {
            this._update(0)
            this._toFlip(this.present.matrix, direction)
            this._update(1)
        }
    }
    // 2.4 旋转方块
    rotate(direction, step = 1) {
        if (this.next("_toRotate", direction, step)) {
            this._update(0)
            this._toRotate(this.present.matrix, direction, step)
            this._update(1)
        }
    }

    /* 3 初始化函数 */
    // 3.1 深拷贝：够用就行
    _deepClone(data) {
        return JSON.parse(JSON.stringify(data))
    }
    // 3.2 创建矩阵
    _newMatrix(length) {
        const matrix = []
        for (let i = 0; i < length; i++) {
            matrix.push([])
            for (let j = 0; j < length; j++) {
                matrix[i].push({ x: j, y: i })
            }
        }
        return matrix
    }
    // 3.3 链接像素指针到矩阵指针
    _linkPixels(matrix, pixels) {
        for (let i = 0; i < 4; i++) {
            matrix[pixels[i].y][pixels[i].x] = pixels[i]
        }
    }
    // 3.4 初始化方块：矩阵指针、像素指针
    _initTetris(type, pointer) {
        this[pointer].pixels = this._deepClone(this.base[type].pixels)
        this[pointer].matrix = this._newMatrix(this.base[type].length)
        this._linkPixels(this[pointer].matrix, this[pointer].pixels)
    }
    // 3.5 重置方块
    reset() {
        // 确定初始类型
        let type = this.types[this._randomIndex(7)]
        // 确定初始状态
        const rotateStep = this._randomIndex(4)
        // 确定初始位置
        const moveRight = this._randomIndex(
            this.data[0].length - this.base[type].length
        )
        // 初始化类型
        this._initTetris(type, "present")
        this._initTetris(type, "predict")
        // 调整状态
        this.rotate("clockwise", rotateStep)
        // 调整位置
        this.move("down", this.base[type].moveDown[rotateStep])
        this.move("right", moveRight)
    }
}
